/*****************************************************************************************
   
   Copyright (c) Microsoft Corporation. All rights reserved.

   Use of this code sample is subject to the terms of the Microsoft
   Public License, a copy of which should always be distributed with
   this file.  You can also access a copy of this license agreement at:
   http://www.microsoft.com/opensource/licenses.mspx#Ms-PL

 ****************************************************************************************/
using System;

namespace Microsoft.CSA.FeedSync.FeedSyncLibrary
{
    public class Sync
    {
        private Microsoft.CSA.FeedSync.FeedSyncLibrary.IFeedSyncItem m_IFeedSyncItem;

        private System.Xml.XmlElement m_XmlElement;

        private string m_ID;
        private string m_NamespacePrefix;

        private uint m_Updates;

        private bool m_NoConflicts = false;
        private bool m_Deleted = false;

        private System.Collections.Generic.SortedList<string, Microsoft.CSA.FeedSync.FeedSyncLibrary.IFeedSyncItem> m_ConflictIFeedSyncItemList = new System.Collections.Generic.SortedList<string, Microsoft.CSA.FeedSync.FeedSyncLibrary.IFeedSyncItem>();

        private System.Collections.Generic.List<Microsoft.CSA.FeedSync.FeedSyncLibrary.History> m_HistoryList = new System.Collections.Generic.List<Microsoft.CSA.FeedSync.FeedSyncLibrary.History>();

        private System.Xml.XmlElement m_ConflictsNodeXmlElement = null;

        public enum CompareResult
        {
            Newer,
            Older,
            EqualWithSameConflictData,
            EqualWithDifferentConflictData
        }

        static public System.Collections.Generic.IList<T> Merge<T>(System.Collections.Generic.IList<T> i_LocalItemList, System.Collections.Generic.IList<T> i_IncomingItemList) where T : Microsoft.CSA.FeedSync.FeedSyncLibrary.IFeedSyncItem, new()
        {
            System.Collections.Generic.List<T> MergedItemList = new System.Collections.Generic.List<T>();

            System.Collections.Generic.Dictionary<string, T> LocalItemDictionary = new System.Collections.Generic.Dictionary<string, T>();
            foreach (T LocalItem in i_LocalItemList)
                LocalItemDictionary[LocalItem.Sync.ID] = LocalItem;

            foreach (T IncomingItem in i_IncomingItemList)
            {
                T MergedItem = IncomingItem;

                if (LocalItemDictionary.ContainsKey(IncomingItem.Sync.ID))
                {
                    T LocalItem = LocalItemDictionary[IncomingItem.Sync.ID];
                    Microsoft.CSA.FeedSync.FeedSyncLibrary.Sync.CompareResult CompareResult = LocalItem.Sync.CompareTo(IncomingItem.Sync);

                    bool NeedToMerge =
                        (CompareResult == Microsoft.CSA.FeedSync.FeedSyncLibrary.Sync.CompareResult.EqualWithDifferentConflictData) ||
                        (CompareResult == Microsoft.CSA.FeedSync.FeedSyncLibrary.Sync.CompareResult.Older) ||
                        ((CompareResult == Microsoft.CSA.FeedSync.FeedSyncLibrary.Sync.CompareResult.Newer) &&
                          (!IncomingItem.Sync.IsSubsumedBy(LocalItem.Sync)));

                    if (!NeedToMerge)
                        continue;

                    MergedItem = Microsoft.CSA.FeedSync.FeedSyncLibrary.Sync.Merge<T>(LocalItem, IncomingItem);
                }

                MergedItemList.Add(MergedItem);
            }

            return MergedItemList;
        }

        static public T Merge<T>(T i_LocalItem, T i_IncomingItem) where T : Microsoft.CSA.FeedSync.FeedSyncLibrary.IFeedSyncItem, new()
        {
            if (i_LocalItem == null)
                throw new System.ArgumentException("i_LocalItem must be non-null!");

            if (i_LocalItem.XmlElement == null)
                throw new System.ArgumentException("i_LocalItem.XmlElement must be non-null!");

            if (i_LocalItem.XmlNamespaceManager == null)
                throw new System.ArgumentException("i_LocalItem.XmlNamespaceManager must be non-null!");

            if (i_LocalItem.Sync == null)
                throw new System.ArgumentException("i_LocalItem.Sync must be non-null!");

            if (i_IncomingItem == null)
                throw new System.ArgumentException("i_IncomingItem must be non-null!");

            if (i_IncomingItem.XmlElement == null)
                throw new System.ArgumentException("i_IncomingItem.XmlElement must be non-null!");

            if (i_IncomingItem.XmlNamespaceManager == null)
                throw new System.ArgumentException("i_IncomingItem.XmlNamespaceManager must be non-null!");

            if (i_IncomingItem.Sync == null)
                throw new System.ArgumentException("i_IncomingItem.Sync must be non-null!");

            if (i_LocalItem.Sync.ID != i_IncomingItem.Sync.ID)
                throw new System.ArgumentException("Sync ID values must match!");

            if (i_LocalItem.Sync.NoConflicts != i_IncomingItem.Sync.NoConflicts)
                throw new System.ArgumentException("Sync NoConflicts values must match!");

            object ClonedItem = i_LocalItem.Clone();
            if (!(ClonedItem is T))
                throw new System.Exception("Cloned LocalItem is not same type as original item!");

            T ClonedLocalItem = (T)ClonedItem;
            if (ClonedLocalItem.Sync == i_LocalItem.Sync)
                throw new System.Exception("Cloned LocalItem can't have same Sync reference!");

            if (ClonedLocalItem.XmlElement == i_LocalItem.XmlElement)
                throw new System.Exception("Cloned LocalItem can't have same XmlElement reference!");

            ClonedItem = i_IncomingItem.Clone();
            if (!(ClonedItem is T))
                throw new System.Exception("Cloned IncomingItem is not same type as original item!");

            T ClonedIncomingItem = (T)ClonedItem;
            if (ClonedIncomingItem.Sync == i_IncomingItem.Sync)
                throw new System.Exception("Cloned IncomingItem can't have same Sync reference!");

            if (ClonedIncomingItem.XmlElement == i_IncomingItem.XmlElement)
                throw new System.Exception("Cloned IncomingItem can't have same XmlElement reference!");

            if (ClonedLocalItem.Sync == ClonedIncomingItem.Sync)
                throw new System.Exception("Cloned LocalItem and cloned IncomingItem can't have same Sync reference!");

            if (ClonedLocalItem.XmlElement == ClonedIncomingItem.XmlElement)
                throw new System.Exception("Cloned LocalItem and cloned IncomingItem can't have same XmlElement reference!");

            System.Collections.Generic.List<T> LocalItemList = new System.Collections.Generic.List<T>();
            LocalItemList.Add(ClonedLocalItem);

            System.Collections.Generic.List<T> IncomingItemList = new System.Collections.Generic.List<T>();
            IncomingItemList.Add(ClonedIncomingItem);

            //  Perform conflict feed item processing (if necessary)
            if (!ClonedIncomingItem.Sync.NoConflicts)
            {
                foreach (Microsoft.CSA.FeedSync.FeedSyncLibrary.IFeedSyncItem LocalConflictIFeedSyncItem in ClonedLocalItem.Sync.Conflicts)
                {
                    System.Xml.XmlElement ClonedLocalConflictItemXmlElement = (System.Xml.XmlElement)LocalConflictIFeedSyncItem.XmlElement.CloneNode(true);
                    T LocalConflictItem = new T();
                    
                    Microsoft.CSA.FeedSync.FeedSyncLibrary.Sync LocalSync = LocalConflictItem.Initialize
                        (
                        ClonedLocalConflictItemXmlElement,
                        LocalConflictIFeedSyncItem.XmlNamespaceManager
                        );

                    if (LocalConflictItem.Sync == null)
                        throw new System.Exception("LocalConflictItem.Sync must be non-null!");

                    LocalItemList.Add(LocalConflictItem);
                }

                foreach (Microsoft.CSA.FeedSync.FeedSyncLibrary.IFeedSyncItem IncomingConflictIFeedSyncItem in ClonedIncomingItem.Sync.Conflicts)
                {
                    T IncomingConflictItem = new T();
                    System.Xml.XmlElement ClonedIncomingConflictItemXmlElement = (System.Xml.XmlElement)IncomingConflictIFeedSyncItem.XmlElement.CloneNode(true);

                    Microsoft.CSA.FeedSync.FeedSyncLibrary.Sync IncomingSync = IncomingConflictItem.Initialize
                        (
                        ClonedIncomingConflictItemXmlElement,
                        IncomingConflictIFeedSyncItem.XmlNamespaceManager
                        );

                    if (IncomingConflictItem.Sync == null)
                        throw new System.Exception("IncomingConflictItem.Sync must be non-null!");

                    IncomingItemList.Add(IncomingConflictItem);
                }
            }

            ClonedLocalItem.Sync.RemoveAllConflictItemNodes();
            ClonedIncomingItem.Sync.RemoveAllConflictItemNodes();

            T WinnerItem = default(T);
            System.Collections.Generic.List<T> MergedItemList = new System.Collections.Generic.List<T>();

            #region Process local list

            //  Iterate local items first, looking for subsumption
            for (int Index = LocalItemList.Count - 1; Index >= 0; --Index)
            {
                T LocalItem = LocalItemList[Index];
                bool Subsumed = false;

                foreach (T IncomingItem in IncomingItemList)
                {
                    if (LocalItem.Sync.IsSubsumedBy(IncomingItem.Sync))
                    {
                        Subsumed = true;
                        break;
                    }
                }

                if (Subsumed)
                {
                    LocalItemList.RemoveAt(Index);
                    continue;
                }

                MergedItemList.Add(LocalItem);

                if (WinnerItem == null)
                    WinnerItem = LocalItem;
                else
                {
                    Microsoft.CSA.FeedSync.FeedSyncLibrary.Sync.CompareResult CompareResult = WinnerItem.Sync.CompareTo(LocalItem.Sync);

                    if (Microsoft.CSA.FeedSync.FeedSyncLibrary.Sync.CompareResult.Newer != CompareResult)
                        WinnerItem = LocalItem;
                }
            }

            #endregion

            #region Process incoming feeditem node list

            //  Iterate incoming items next, looking for subsumption
            for (int Index = IncomingItemList.Count - 1; Index >= 0; --Index)
            {
                T IncomingItem = IncomingItemList[Index];

                bool Subsumed = false;

                foreach (T LocalItem in LocalItemList)
                {
                    if (IncomingItem.Sync.IsSubsumedBy(LocalItem.Sync))
                    {
                        Subsumed = true;
                        break;
                    }
                }

                if (Subsumed)
                {
                    IncomingItemList.RemoveAt(Index);
                    continue;
                }

                MergedItemList.Add(IncomingItem);

                if (WinnerItem == null)
                    WinnerItem = IncomingItem;
                else
                {
                    Microsoft.CSA.FeedSync.FeedSyncLibrary.Sync.CompareResult CompareResult = WinnerItem.Sync.CompareTo(IncomingItem.Sync);

                    if (Microsoft.CSA.FeedSync.FeedSyncLibrary.Sync.CompareResult.Newer != CompareResult)
                        WinnerItem = IncomingItem;
                }
            }

            #endregion

            foreach (T ConflictItem in MergedItemList)
            {
                if (!WinnerItem.Equals(ConflictItem))
                    WinnerItem.Sync.AddConflictItem<T>(ConflictItem);
            }

            return WinnerItem;
        }

        public Sync(Microsoft.CSA.FeedSync.FeedSyncLibrary.IFeedSyncItem i_IFeedSyncItem)
        {
            if (i_IFeedSyncItem == null)
                throw new System.ArgumentException("i_IFeedSyncItem must be non-null!");

            if (i_IFeedSyncItem.XmlElement == null)
                throw new System.ArgumentException("i_IFeedSyncItem.XmlElement must be non-null!");

            if (i_IFeedSyncItem.XmlNamespaceManager == null)
                throw new System.ArgumentException("i_IFeedSyncItem.XmlNamespaceManager must be non-null!");

            m_NamespacePrefix = Microsoft.CSA.FeedSync.FeedSyncLibrary.XmlNamespaceManagerHelper.InitializeXmlNamespaceManager
                (
                i_IFeedSyncItem.XmlElement.OwnerDocument, 
                i_IFeedSyncItem.XmlNamespaceManager,
                Microsoft.CSA.FeedSync.FeedSyncLibrary.Constants.FEEDSYNC_XML_NAMESPACE_PREFIX,
                Microsoft.CSA.FeedSync.FeedSyncLibrary.Constants.FEEDSYNC_XML_NAMESPACE_URI
                );

            string XPathQuery = System.String.Format
                (
                "{0}:{1}",
                m_NamespacePrefix,
                Microsoft.CSA.FeedSync.FeedSyncLibrary.Constants.SYNC_ELEMENT_NAME
                );

            System.Xml.XmlElement SyncXmlElement = (System.Xml.XmlElement)i_IFeedSyncItem.XmlElement.SelectSingleNode(XPathQuery, i_IFeedSyncItem.XmlNamespaceManager);
            if (SyncXmlElement == null)
                throw new System.ArgumentException("Invalid item - does not contain a FeedSync sync element!");

            this.InitializeFromXmlElement(i_IFeedSyncItem, SyncXmlElement);
        }

        public Sync(Microsoft.CSA.FeedSync.FeedSyncLibrary.IFeedSyncItem i_IFeedSyncItem, System.Xml.XmlElement i_SyncXmlElement)
        {
            if (i_IFeedSyncItem == null)
                throw new System.ArgumentException("i_IFeedSyncItem must be non-null!");

            if (i_IFeedSyncItem.XmlElement == null)
                throw new System.ArgumentException("i_IFeedSyncItem.XmlElement must be non-null!");

            if (i_IFeedSyncItem.XmlNamespaceManager == null)
                throw new System.ArgumentException("i_IFeedSyncItem.XmlNamespaceManager must be non-null!");

            if (i_SyncXmlElement == null)
                throw new System.ArgumentException("i_SyncXmlElement must be non-null!");

            if (i_SyncXmlElement.OwnerDocument != i_IFeedSyncItem.XmlElement.OwnerDocument)
                i_SyncXmlElement  = (System.Xml.XmlElement)i_IFeedSyncItem.XmlElement.OwnerDocument.ImportNode(i_SyncXmlElement, true);

            this.InitializeFromXmlElement(i_IFeedSyncItem, i_SyncXmlElement);
        }

        public Sync(System.Xml.XmlDocument i_XmlDocument, System.Xml.XmlNamespaceManager i_XmlNamespaceManager, string i_By, string i_ID)
            : this(i_XmlDocument, i_XmlNamespaceManager, i_By, i_ID, 0, false, false)
        {
        }

        public Sync(System.Xml.XmlDocument i_XmlDocument, System.Xml.XmlNamespaceManager i_XmlNamespaceManager, string i_By, string i_ID, uint i_Updates, bool i_Deleted, bool i_NoConflicts)
        {
            if (i_XmlDocument == null)
                throw new System.ArgumentException("i_XmlDocument must be non-null!");

            m_NamespacePrefix = Microsoft.CSA.FeedSync.FeedSyncLibrary.XmlNamespaceManagerHelper.InitializeXmlNamespaceManager
                (
                i_XmlDocument,
                i_XmlNamespaceManager,
                Microsoft.CSA.FeedSync.FeedSyncLibrary.Constants.FEEDSYNC_XML_NAMESPACE_PREFIX,
                Microsoft.CSA.FeedSync.FeedSyncLibrary.Constants.FEEDSYNC_XML_NAMESPACE_URI
                );

            if (System.String.IsNullOrEmpty(m_NamespacePrefix))
            {
                i_XmlDocument.DocumentElement.SetAttribute
                    (
                    "xmlns:" + Microsoft.CSA.FeedSync.FeedSyncLibrary.Constants.FEEDSYNC_XML_NAMESPACE_PREFIX,
                    Microsoft.CSA.FeedSync.FeedSyncLibrary.Constants.FEEDSYNC_XML_NAMESPACE_URI
                    );

                m_NamespacePrefix = Microsoft.CSA.FeedSync.FeedSyncLibrary.XmlNamespaceManagerHelper.InitializeXmlNamespaceManager
                    (
                    i_XmlDocument,
                    i_XmlNamespaceManager,
                    Microsoft.CSA.FeedSync.FeedSyncLibrary.Constants.FEEDSYNC_XML_NAMESPACE_PREFIX,
                    Microsoft.CSA.FeedSync.FeedSyncLibrary.Constants.FEEDSYNC_XML_NAMESPACE_URI
                    );
            }

            string XPathQuery = System.String.Format
                (
                "{0}:{1}",
                m_NamespacePrefix,
                Microsoft.CSA.FeedSync.FeedSyncLibrary.Constants.SYNC_ELEMENT_NAME
                );

            m_ID = i_ID;
            m_Updates = i_Updates;
            m_Deleted = i_Deleted;
            m_NoConflicts = i_NoConflicts;
            m_HistoryList = new System.Collections.Generic.List<History>();
            m_ConflictIFeedSyncItemList = new System.Collections.Generic.SortedList<string, IFeedSyncItem>();

            string SyncElementName = System.String.Format
                (
                "{0}:{1}",
                m_NamespacePrefix,
                Microsoft.CSA.FeedSync.FeedSyncLibrary.Constants.SYNC_ELEMENT_NAME
                );

            m_XmlElement = i_XmlDocument.CreateElement(SyncElementName, Microsoft.CSA.FeedSync.FeedSyncLibrary.Constants.FEEDSYNC_XML_NAMESPACE_URI);
            m_XmlElement.SetAttribute(Microsoft.CSA.FeedSync.FeedSyncLibrary.Constants.ID_ATTRIBUTE, m_ID);
            m_XmlElement.SetAttribute(Microsoft.CSA.FeedSync.FeedSyncLibrary.Constants.UPDATES_ATTRIBUTE, m_Updates.ToString());

            if (m_Deleted)
                m_XmlElement.SetAttribute(Microsoft.CSA.FeedSync.FeedSyncLibrary.Constants.DELETED_ATTRIBUTE, "true");

            if (m_NoConflicts)
                m_XmlElement.SetAttribute(Microsoft.CSA.FeedSync.FeedSyncLibrary.Constants.NO_CONFLICTS_ATTRIBUTE, "true");

            this.Update
                (
                System.DateTime.Now,
                i_By
                );

            this.Validate();
        }

        private void InitializeFromXmlElement(Microsoft.CSA.FeedSync.FeedSyncLibrary.IFeedSyncItem i_IFeedSyncItem, System.Xml.XmlElement i_SyncXmlElement)
        {
            bool InvalidElement =
                (i_SyncXmlElement.LocalName != Microsoft.CSA.FeedSync.FeedSyncLibrary.Constants.SYNC_ELEMENT_NAME) ||
                (i_SyncXmlElement.NamespaceURI != Microsoft.CSA.FeedSync.FeedSyncLibrary.Constants.FEEDSYNC_XML_NAMESPACE_URI);

            if (InvalidElement)
                throw new System.ArgumentException("Invalid i_SyncXmlElement!");

            m_IFeedSyncItem = i_IFeedSyncItem;
            m_XmlElement = i_SyncXmlElement;

            m_NamespacePrefix = Microsoft.CSA.FeedSync.FeedSyncLibrary.XmlNamespaceManagerHelper.InitializeXmlNamespaceManager
                (
                m_IFeedSyncItem.XmlElement.OwnerDocument,
                m_IFeedSyncItem.XmlNamespaceManager,
                Microsoft.CSA.FeedSync.FeedSyncLibrary.Constants.FEEDSYNC_XML_NAMESPACE_PREFIX,
                Microsoft.CSA.FeedSync.FeedSyncLibrary.Constants.FEEDSYNC_XML_NAMESPACE_URI
                );

            m_ID = m_XmlElement.GetAttribute(Microsoft.CSA.FeedSync.FeedSyncLibrary.Constants.ID_ATTRIBUTE);
            m_Updates = System.Convert.ToUInt32(m_XmlElement.GetAttribute(Microsoft.CSA.FeedSync.FeedSyncLibrary.Constants.UPDATES_ATTRIBUTE));

            if (m_XmlElement.HasAttribute(Microsoft.CSA.FeedSync.FeedSyncLibrary.Constants.DELETED_ATTRIBUTE))
                m_Deleted = (m_XmlElement.GetAttribute(Microsoft.CSA.FeedSync.FeedSyncLibrary.Constants.DELETED_ATTRIBUTE) == "true");

            if (m_XmlElement.HasAttribute(Microsoft.CSA.FeedSync.FeedSyncLibrary.Constants.NO_CONFLICTS_ATTRIBUTE))
                m_NoConflicts = (m_XmlElement.GetAttribute(Microsoft.CSA.FeedSync.FeedSyncLibrary.Constants.NO_CONFLICTS_ATTRIBUTE) == "true");

            string XPathQuery = System.String.Format
                (
                "{0}:{1}",
                m_NamespacePrefix,
                Microsoft.CSA.FeedSync.FeedSyncLibrary.Constants.HISTORY_ELEMENT_NAME
                );

            System.Xml.XmlNodeList HistoryList = i_SyncXmlElement.SelectNodes
                (
                XPathQuery,
                m_IFeedSyncItem.XmlNamespaceManager
                );

            foreach (System.Xml.XmlElement HistoryXmlElement in HistoryList)
            {
                Microsoft.CSA.FeedSync.FeedSyncLibrary.History History = new Microsoft.CSA.FeedSync.FeedSyncLibrary.History(HistoryXmlElement);

                m_HistoryList.Add(History);
            }

            if (!m_NoConflicts)
            {
                XPathQuery = System.String.Format
                    (
                    "{0}:{1}",
                    m_NamespacePrefix,
                    Microsoft.CSA.FeedSync.FeedSyncLibrary.Constants.CONFLICTS_ELEMENT_NAME
                    );

                m_ConflictsNodeXmlElement = (System.Xml.XmlElement)i_SyncXmlElement.SelectSingleNode
                    (
                    XPathQuery,
                    m_IFeedSyncItem.XmlNamespaceManager
                    );

                if (m_ConflictsNodeXmlElement != null)
                {
                    XPathQuery = System.String.Format
                        (
                        "*/{0}:{1}",
                        m_NamespacePrefix,
                        Microsoft.CSA.FeedSync.FeedSyncLibrary.Constants.SYNC_ELEMENT_NAME
                        );

                    System.Xml.XmlNodeList ConflictSyncXmlNodeList = m_ConflictsNodeXmlElement.SelectNodes
                        (
                        XPathQuery,
                        m_IFeedSyncItem.XmlNamespaceManager
                        );

                    System.Type ConflictItemType = i_IFeedSyncItem.GetType();

                    foreach (System.Xml.XmlElement ConflictSyncXmlElement in ConflictSyncXmlNodeList)
                    {
                        Microsoft.CSA.FeedSync.FeedSyncLibrary.IFeedSyncItem ConflictFeedSyncItem = (Microsoft.CSA.FeedSync.FeedSyncLibrary.IFeedSyncItem)System.Activator.CreateInstance(ConflictItemType);

                        ConflictFeedSyncItem.Initialize
                            (
                            (System.Xml.XmlElement)ConflictSyncXmlElement.ParentNode,
                            m_IFeedSyncItem.XmlNamespaceManager
                            );
                            
                        string Key = System.String.Format
                            (
                            "{0}{1}{2}",
                            ConflictFeedSyncItem.Sync.Updates,
                            ConflictFeedSyncItem.Sync.TopMostHistory.Sequence,
                            ConflictFeedSyncItem.Sync.TopMostHistory.By
                            );

                        if (ConflictFeedSyncItem.Sync.TopMostHistory.WhenDateTime != null)
                            Key += ((System.DateTime)ConflictFeedSyncItem.Sync.TopMostHistory.WhenDateTime);

                        if (!m_ConflictIFeedSyncItemList.ContainsKey(Key))
                            m_ConflictIFeedSyncItemList.Add(Key, ConflictFeedSyncItem);
                    }
                }
            }

            this.Validate();
        }

        private void Validate()
        {
            if (System.String.IsNullOrEmpty(m_ID))
                throw new System.ArgumentException("Invalid ID!");

            if ((m_Updates < 1) || (m_Updates > (System.Math.Pow(2, 32) - 1)))
                throw new System.ArgumentException("Invalid Updates!");

            if (m_HistoryList == null)
                throw new System.ArgumentException("HistoryList must be non-null!");

            if (m_ConflictIFeedSyncItemList == null)
                throw new System.ArgumentException("ConflictFeedSyncItemList must be non-null!");
        }

        public Microsoft.CSA.FeedSync.FeedSyncLibrary.Sync.CompareResult CompareTo(Microsoft.CSA.FeedSync.FeedSyncLibrary.Sync i_IncomingSync)
        {
            if (i_IncomingSync.Updates > m_Updates)
                return Microsoft.CSA.FeedSync.FeedSyncLibrary.Sync.CompareResult.Older;

            if (i_IncomingSync.Updates == m_Updates)
            {
                if (!this.TopMostHistory.WhenDateTime.HasValue && i_IncomingSync.TopMostHistory.WhenDateTime.HasValue)
                    return Microsoft.CSA.FeedSync.FeedSyncLibrary.Sync.CompareResult.Older;

                int CompareOrdinalResult = 0;

                if (this.TopMostHistory.WhenDateTime.HasValue && i_IncomingSync.TopMostHistory.WhenDateTime.HasValue)
                {
                    //  --------------------------------------------------------------------------------------------------
                    //  BIG HONKING NOTE:  Since dates are normalized to RFC3339 form, just compare string values to avoid
                    //                     .NET bugs with DateTime[Offset] comparisons
                    //  --------------------------------------------------------------------------------------------------
                    string LocalSyncWhen = this.TopMostHistory.WhenDateTime.Value.ToString(Microsoft.CSA.FeedSync.FeedSyncLibrary.Constants.DATE_STRING_FORMAT);
                    string IncomingSyncWhen = i_IncomingSync.TopMostHistory.WhenDateTime.Value.ToString(Microsoft.CSA.FeedSync.FeedSyncLibrary.Constants.DATE_STRING_FORMAT);

                    CompareOrdinalResult = System.String.CompareOrdinal(LocalSyncWhen, IncomingSyncWhen);
                    if (CompareOrdinalResult < 0)
                        return Microsoft.CSA.FeedSync.FeedSyncLibrary.Sync.CompareResult.Older;

                    if (CompareOrdinalResult > 0)
                        return Microsoft.CSA.FeedSync.FeedSyncLibrary.Sync.CompareResult.Newer;
                }

                CompareOrdinalResult = System.String.CompareOrdinal(this.TopMostHistory.By, i_IncomingSync.TopMostHistory.By);
                if (CompareOrdinalResult > 0)
                    return Microsoft.CSA.FeedSync.FeedSyncLibrary.Sync.CompareResult.Older;

                if (CompareOrdinalResult < 0)
                    return Microsoft.CSA.FeedSync.FeedSyncLibrary.Sync.CompareResult.Newer;

                if (m_ConflictIFeedSyncItemList.Count == i_IncomingSync.Conflicts.Count)
                {
                    if (m_ConflictIFeedSyncItemList.Count > 0)
                    {
                        foreach (Microsoft.CSA.FeedSync.FeedSyncLibrary.IFeedSyncItem ConflictIFeedSyncItem in m_ConflictIFeedSyncItemList.Values)
                        {
                            bool MatchingConflictItem = false;

                            foreach (Microsoft.CSA.FeedSync.FeedSyncLibrary.IFeedSyncItem IncomingConflictIFeedSyncItem in i_IncomingSync.Conflicts)
                            {
                                if (IncomingConflictIFeedSyncItem.Sync.CompareTo(ConflictIFeedSyncItem.Sync) == Microsoft.CSA.FeedSync.FeedSyncLibrary.Sync.CompareResult.EqualWithSameConflictData)
                                {
                                    MatchingConflictItem = true;
                                    break;
                                }
                            }

                            if (!MatchingConflictItem)
                                return Microsoft.CSA.FeedSync.FeedSyncLibrary.Sync.CompareResult.EqualWithDifferentConflictData;
                        }
                    }

                    return Microsoft.CSA.FeedSync.FeedSyncLibrary.Sync.CompareResult.EqualWithSameConflictData;
                }
                else
                {
                    return Microsoft.CSA.FeedSync.FeedSyncLibrary.Sync.CompareResult.EqualWithDifferentConflictData;
                }
            }

            return Microsoft.CSA.FeedSync.FeedSyncLibrary.Sync.CompareResult.Newer;
        }

        public bool IsSubsumedBy(Microsoft.CSA.FeedSync.FeedSyncLibrary.Sync i_IncomingSync)
        {
            bool IsSubsumed = false;

            //  Compare local item topmost history to incoming item history
            foreach (Microsoft.CSA.FeedSync.FeedSyncLibrary.History IncomingHistory in i_IncomingSync.Histories)
            {
                if (this.TopMostHistory.IsSubsumedBy(IncomingHistory))
                {
                    IsSubsumed = true;
                    break;
                }
            }

            if (!IsSubsumed)
            {
                //  Compare local item incoming item conflict
                foreach (Microsoft.CSA.FeedSync.FeedSyncLibrary.IFeedSyncItem IncomingConflictIFeedSyncItem in i_IncomingSync.Conflicts)
                {
                    if (this.IsSubsumedBy(IncomingConflictIFeedSyncItem.Sync))
                    {
                        IsSubsumed = true;
                        break;
                    }
                }
            }

            if (!IsSubsumed)
                return false;


            foreach (Microsoft.CSA.FeedSync.FeedSyncLibrary.IFeedSyncItem ConflictIFeedSyncItem in this.Conflicts)
            {
                IsSubsumed = false;

                //  Compare local item conflict to incoming item
                if (ConflictIFeedSyncItem.Sync.IsSubsumedBy(i_IncomingSync))
                {
                    IsSubsumed = true;
                    continue;
                }

                // Compare local item conflict to incoming item conflict
                foreach (Microsoft.CSA.FeedSync.FeedSyncLibrary.IFeedSyncItem IncomingConflictIFeedSyncItem in i_IncomingSync.Conflicts)
                {
                    if (ConflictIFeedSyncItem.Sync.IsSubsumedBy(IncomingConflictIFeedSyncItem.Sync))
                    {
                        IsSubsumed = true;
                        break;
                    }
                }

                if (!IsSubsumed)
                    return false;
            }

            return true;
        }

        public void RemoveAllConflictItemNodes()
        {
            if (m_ConflictIFeedSyncItemList.Count == 0)
                return;

            //  Delete "sx:conflicts" element
            m_ConflictsNodeXmlElement.ParentNode.RemoveChild(m_ConflictsNodeXmlElement);
            m_ConflictsNodeXmlElement = null;

            //  Empty node list
            m_ConflictIFeedSyncItemList.Clear();
        }

        public void AddConflictItem<T>(T i_ConflictFeedSyncItem) where T : Microsoft.CSA.FeedSync.FeedSyncLibrary.IFeedSyncItem, new()
        {
            if (i_ConflictFeedSyncItem.XmlElement.OwnerDocument != m_XmlElement.OwnerDocument)
            {
                System.Xml.XmlElement ImportedConflictFeedSyncItemXmlElement = (System.Xml.XmlElement)m_XmlElement.OwnerDocument.ImportNode
                    (
                    i_ConflictFeedSyncItem.XmlElement,
                    true
                    );

                T ImportedConflictFeedSyncItem = new T();

                ImportedConflictFeedSyncItem.Initialize
                    (
                    ImportedConflictFeedSyncItemXmlElement,
                    m_IFeedSyncItem.XmlNamespaceManager
                    );

                i_ConflictFeedSyncItem = ImportedConflictFeedSyncItem;
            }

            string Key = System.String.Format
                (
                "{0}{1}{2}",
                i_ConflictFeedSyncItem.Sync.Updates,
                i_ConflictFeedSyncItem.Sync.TopMostHistory.Sequence,
                i_ConflictFeedSyncItem.Sync.TopMostHistory.By
                );

            if (i_ConflictFeedSyncItem.Sync.TopMostHistory.WhenDateTime != null)
                Key += ((System.DateTime)i_ConflictFeedSyncItem.Sync.TopMostHistory.WhenDateTime);

            //  Check if if item already exists in either list
            if (m_ConflictIFeedSyncItemList.ContainsKey(Key))
            {
                if (m_ConflictIFeedSyncItemList.ContainsKey(Key))
                    throw new System.ArgumentException("SyncNode::AddConflictItem (" + Key + ") - item already exists as conflict");
            }

            //  Create "sx:conflicts" element if necessary
            if (m_ConflictsNodeXmlElement == null)
            {
                string ElementName = System.String.Format
                    (
                    "{0}:{1}",
                    m_NamespacePrefix,
                    Microsoft.CSA.FeedSync.FeedSyncLibrary.Constants.CONFLICTS_ELEMENT_NAME
                    );

                m_ConflictsNodeXmlElement = m_IFeedSyncItem.XmlElement.OwnerDocument.CreateElement
                    (
                    ElementName,
                    Microsoft.CSA.FeedSync.FeedSyncLibrary.Constants.FEEDSYNC_XML_NAMESPACE_URI
                    );

                m_XmlElement.AppendChild(m_ConflictsNodeXmlElement);
            }

            //  Append conflict node's element to "sx:conflicts" element
            m_ConflictsNodeXmlElement.AppendChild(i_ConflictFeedSyncItem.XmlElement);

            //  Add node to list
            m_ConflictIFeedSyncItemList.Add(Key, i_ConflictFeedSyncItem);
        }

        public bool DoesConflictItemNodeExist(Microsoft.CSA.FeedSync.FeedSyncLibrary.IFeedSyncItem i_IFeedSyncItem)
        {
            string Key = System.String.Format
                (
                "{0}{1}{2}",
                i_IFeedSyncItem.Sync.Updates,
                i_IFeedSyncItem.Sync.TopMostHistory.Sequence,
                i_IFeedSyncItem.Sync.TopMostHistory.By
                );

            if (i_IFeedSyncItem.Sync.TopMostHistory.WhenDateTime != null)
                Key += ((System.DateTime)i_IFeedSyncItem.Sync.TopMostHistory.WhenDateTime);

            return (m_ConflictIFeedSyncItemList.ContainsKey(Key));
        }

        public string ID
        {
            get
            {
                return m_ID;
            }
        }

        public uint Updates
        {
            get
            {
                return m_Updates;
            }
            set
            {
                if ((value < m_Updates) || (value > (System.Math.Pow(2, 32) -1)))
                    throw new System.ArgumentException("Invalid value!");

                m_XmlElement.SetAttribute
                    (
                    Microsoft.CSA.FeedSync.FeedSyncLibrary.Constants.UPDATES_ATTRIBUTE,
                    value.ToString()
                    );

                m_Updates = value;
            }
        }

        public bool NoConflicts
        {
            get
            {
                return m_NoConflicts;
            }
        }

        public bool Deleted
        {
            get
            {
                return m_Deleted;
            }
            set
            {
                if (value == m_Deleted)
                    return;

                m_XmlElement.SetAttribute
                    (
                    Microsoft.CSA.FeedSync.FeedSyncLibrary.Constants.DELETED_ATTRIBUTE,
                    value ? "true" : "false"
                    );

                m_Deleted = value;
            }
        }

        public Microsoft.CSA.FeedSync.FeedSyncLibrary.History TopMostHistory
        {
            get
            {
                if (m_HistoryList.Count == 0)
                    return null;

                return m_HistoryList[0];
            }
        }

        public Microsoft.CSA.FeedSync.FeedSyncLibrary.History[] Histories
        {
            get
            {
                return m_HistoryList.ToArray();
            }
        }

        public System.Collections.Generic.IList<Microsoft.CSA.FeedSync.FeedSyncLibrary.IFeedSyncItem> Conflicts
        {
            get
            {
                return m_ConflictIFeedSyncItemList.Values;
            }
        }

        public Microsoft.CSA.FeedSync.FeedSyncLibrary.IFeedSyncItem IFeedSyncItem
        {
            get
            {
                return m_IFeedSyncItem;
            }
        }

        public System.Xml.XmlElement XmlElement
        {
            get
            {
                return m_XmlElement;
            }
        }

        public string NamespacePrefix
        {
            get
            {
                return m_NamespacePrefix;
            }
        }

        public void Update(System.DateTime? i_WhenDateTime, string i_By)
        {
            this.Update(i_WhenDateTime, i_By, true, false, false);
        }

        public void Update(System.DateTime? i_WhenDateTime, string i_By, bool i_SparsePurge)
        {
            this.Update(i_WhenDateTime, i_By, i_SparsePurge, false, false);
        }

        public void ResolveConflicts(System.DateTime? i_WhenDateTime, string i_By)
        {
            this.Update(i_WhenDateTime, i_By, true, true, false);
        }

        public void ResolveConflicts(System.DateTime? i_WhenDateTime, string i_By, bool i_SparsePurge)
        {
            this.Update(i_WhenDateTime, i_By, i_SparsePurge, true, false);
        }

        public void Delete(System.DateTime? i_WhenDateTime, string i_By)
        {
            this.Update(i_WhenDateTime, i_By, true, false, true);
        }

        public void Delete(System.DateTime? i_WhenDateTime, string i_By, bool i_SparsePurge)
        {
            this.Update(i_WhenDateTime, i_By, i_SparsePurge, false, true);
        }

        private void Update(System.DateTime? i_WhenDateTime, string i_By, bool i_SparsePurge, bool i_ResolveConflicts, bool i_Delete)
        {
            if (!i_WhenDateTime.HasValue && System.String.IsNullOrEmpty(i_By))
                throw new System.ArgumentException("Must have a valid i_WhenDateTime or i_By!");

            if (i_SparsePurge && !System.String.IsNullOrEmpty(i_By))
            {
                for (int Index = m_HistoryList.Count - 1; Index >= 0; --Index)
                {
                    Microsoft.CSA.FeedSync.FeedSyncLibrary.History History = m_HistoryList[Index];
                    if (System.String.CompareOrdinal(History.By, i_By) == 0)
                        this.RemoveHistory(History);
                }
            }

            this.Updates++;
            this.Deleted = i_Delete;

            uint Sequence = this.Updates;

            if (!System.String.IsNullOrEmpty(i_By))
            {
                //  Check the histories of the winning item to see if any of them have a sequence
                //  value that is >= sequence assigned above - if so compensate
                foreach (Microsoft.CSA.FeedSync.FeedSyncLibrary.History History in m_HistoryList)
                {
                    if ((System.String.CompareOrdinal(History.By, i_By) == 0) && (History.Sequence >= Sequence))
                        Sequence = History.Sequence + 1;
                }


                //  ************************************************************************************
                //  BIG HONKING NOTE:  We must check for conflict items that are subsumed by this update
                //  ************************************************************************************
                for (int Index = m_ConflictIFeedSyncItemList.Values.Count - 1; Index >= 0; --Index)
                {
                    Microsoft.CSA.FeedSync.FeedSyncLibrary.IFeedSyncItem ConflictIFeedSyncItem = m_ConflictIFeedSyncItemList.Values[Index];
                    if (ConflictIFeedSyncItem.Sync.TopMostHistory.By == i_By)
                        this.RemoveConflict(ConflictIFeedSyncItem);
                }
            }

            Microsoft.CSA.FeedSync.FeedSyncLibrary.History History2 = new Microsoft.CSA.FeedSync.FeedSyncLibrary.History
                (
                this,
                Sequence,
                i_WhenDateTime,
                i_By
                );

            this.AddHistory(History2);

            bool IgnoreConflictProcessing =
                m_NoConflicts ||
                !i_ResolveConflicts ||
                (m_ConflictIFeedSyncItemList.Count == 0);

            if (IgnoreConflictProcessing)
                return;

            //  *********************************************************************************
            //  BIG HONKING NOTE:  This code resolves all conflicts and does not accomodate for
            //                     selective conflict resolution
            //  *********************************************************************************

            foreach (Microsoft.CSA.FeedSync.FeedSyncLibrary.IFeedSyncItem ConflictIFeedSyncItem in m_ConflictIFeedSyncItemList.Values)
            {
                foreach (Microsoft.CSA.FeedSync.FeedSyncLibrary.History ConflictHistory in ConflictIFeedSyncItem.Sync.Histories)
                {
                    bool Subsumed = false;

                    foreach (Microsoft.CSA.FeedSync.FeedSyncLibrary.History History in m_HistoryList)
                    {
                        if (ConflictHistory.IsSubsumedBy(History))
                        {
                            Subsumed = true;
                            break;
                        }

                        if ((System.String.CompareOrdinal(History.By, i_By) == 0) && (History.Sequence >= Sequence))
                        {
                            History2.Sequence = History.Sequence + 1;
                            break;
                        }
                    }

                    if (!Subsumed)
                        this.AddConflictHistory(ConflictHistory);
                }
            }

            this.RemoveAllConflictItemNodes();
        }

        internal void AddHistory(Microsoft.CSA.FeedSync.FeedSyncLibrary.History i_History)
        {
            if (m_HistoryList.Contains(i_History))
                throw new System.ArgumentException("i_History already added!");

            if (i_History.XmlElement.OwnerDocument != m_XmlElement.OwnerDocument)
            {
                System.Xml.XmlElement ImportedHistoryXmlElement = (System.Xml.XmlElement)m_XmlElement.OwnerDocument.ImportNode
                    (
                    i_History.XmlElement,
                    true
                    );

                i_History = new Microsoft.CSA.FeedSync.FeedSyncLibrary.History(ImportedHistoryXmlElement);
            }

            if (m_XmlElement.ChildNodes.Count > 0)
            {
                m_XmlElement.InsertBefore
                    (
                    i_History.XmlElement,
                    m_XmlElement.ChildNodes[0]
                    );
            }
            else
                m_XmlElement.AppendChild(i_History.XmlElement);

            //  Make sure new history node is first node in list
            m_HistoryList.Insert
                (
                0,
                i_History
                );

            i_History.Sync = this;
        }

        internal void AddConflictHistory(Microsoft.CSA.FeedSync.FeedSyncLibrary.History i_ConflictHistory)
        {
            if (i_ConflictHistory.XmlElement.OwnerDocument != m_XmlElement.OwnerDocument)
            {
                System.Xml.XmlElement ImportedConflictHistoryXmlElement = (System.Xml.XmlElement)m_XmlElement.OwnerDocument.ImportNode
                    (
                    i_ConflictHistory.XmlElement,
                    true
                    );

                i_ConflictHistory = new Microsoft.CSA.FeedSync.FeedSyncLibrary.History(ImportedConflictHistoryXmlElement);
            }

            //  Insert after topmost history element
            m_XmlElement.InsertBefore
                (
                i_ConflictHistory.XmlElement,
                this.TopMostHistory.XmlElement.NextSibling
                );

            for (int Index = m_HistoryList.Count - 1; Index >= 0; --Index)
            {
                Microsoft.CSA.FeedSync.FeedSyncLibrary.History History = m_HistoryList[Index];
                if (History.IsSubsumedBy(i_ConflictHistory))
                    this.RemoveHistory(History);
            }

            m_HistoryList.Add(i_ConflictHistory);

            i_ConflictHistory.Sync = this;
        }

        internal void RemoveHistory(Microsoft.CSA.FeedSync.FeedSyncLibrary.History i_History)
        {
            i_History.XmlElement.ParentNode.RemoveChild(i_History.XmlElement);
            m_HistoryList.Remove(i_History);
        }

        internal void RemoveConflict(Microsoft.CSA.FeedSync.FeedSyncLibrary.IFeedSyncItem i_ConflictIFeedSyncItem)
        {
            i_ConflictIFeedSyncItem.XmlElement.ParentNode.RemoveChild(i_ConflictIFeedSyncItem.XmlElement);
            m_ConflictIFeedSyncItemList.Remove(i_ConflictIFeedSyncItem.Sync.ID);
        }

    }
}
